/*   The MIT License
 *   
 *   Carina Engine
 *   Copyright (c) 2009 2010 2011 2012 Zdravko Velinov
 *   
 *   Permission is hereby granted, free of charge, to any person obtaining a copy
 *   of this software and associated documentation files (the "Software"), to deal
 *   in the Software without restriction, including without limitation the rights
 *   to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 *   copies of the Software, and to permit persons to whom the Software is
 *   furnished to do so, subject to the following conditions:
 *
 *   The above copyright notice and this permission notice shall be included in
 *   all copies or substantial portions of the Software.
 *
 *   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 *   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 *   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 *   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 *   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 *   OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 *   THE SOFTWARE.
 */

#include "carina/dx-renderer.hh"

#include "carina/common/exception.hh"
#include "carina/common/memory.hh"
#include "carina/utils.hh"

#include "d3dx11effect.h"

namespace Carina
{
HLSLShaderCompiler::HLSLShaderCompiler(DXRenderer& renderer)
    :   ShaderCompiler("HLSL"),
        m_Renderer(renderer)
{
}

HLSLShaderCompiler::~HLSLShaderCompiler()
{
}

ShaderProgramPtr HLSLShaderCompiler::compileShaderProgram(const string& filename) const
{
    return make_aligned_shared<DXShaderProgram>(m_Renderer, filename);
}

DXInputLayout::DXInputLayout(DXRenderer& renderer, const ShaderProgramPtr& shader, const InputElementArray& arr)
{
    BYTE    *sign;
    SIZE_T  sign_size;
    string  name;
    DynamicArray<D3D11_INPUT_ELEMENT_DESC> dxarr;
    dxarr.resize(arr.size());
    for(size_t i = 0; i < arr.size(); ++i)
    {
        dxarr[i].SemanticName = arr[i].m_Name.c_str();
        dxarr[i].SemanticIndex = 0;
        dxarr[i].Format = D3DTranslateVarType(arr[i].m_Type);
        dxarr[i].InputSlot = 0;
        dxarr[i].AlignedByteOffset = arr[i].m_Offset;
        dxarr[i].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA;
        dxarr[i].InstanceDataStepRate = 0;
    }
    static_cast<const DXShaderProgram&>(*shader).getInputSignature(sign, sign_size);
    HR_ASSERT(renderer.getD3DDevice().CreateInputLayout(dxarr.get(), dxarr.size(), sign, sign_size, &m_InputLayout));
}

void DXInputLayout::setup(DXRenderer& renderer) const
{
    renderer.getD3DCurrentDeviceContext().IASetInputLayout(m_InputLayout);
}

DXShaderProgram::DXShaderProgram(DXRenderer& renderer, const string& name)
    :   m_Renderer(renderer)
{
    reload(name);
}

void DXShaderProgram::reload(const string& filename)
{
	ComRAII<ID3DBlob> compErrors;
    ComRAII<ID3DBlob> effectBlob;
#ifndef NDEBUG
    const UINT hlsl_flags = D3DCOMPILE_DEBUG | D3DCOMPILE_ENABLE_STRICTNESS; //| D3DCOMPILE_WARNINGS_ARE_ERRORS;
#else
    const UINT hlsl_flags = D3DCOMPILE_OPTIMIZATION_LEVEL3 | D3DCOMPILE_ENABLE_STRICTNESS;
#endif
    string filecontents;
	auto ret = CopyFileToString(filename, filecontents);
	CE_ASSERT(ret, "The application cannot open or read the the specified file");
    if(!ret)
		return;
	HRESULT hr = D3DCompile(filecontents.c_str(), filecontents.size(), filename.c_str(), nullptr, &m_Renderer.getIncludeHandler(), nullptr,
                            "fx_5_0", hlsl_flags, 0, &effectBlob, &compErrors);

	m_Effect.release();
	if(FAILED(hr) || compErrors)
    {
        string error("An error occurred while loading this file: " + filename);
        if((ID3D10Blob*)compErrors)
            error += string(":\n\n") + reinterpret_cast<char*>(compErrors->GetBufferPointer());
		CE_ASSERT(false, error);
		m_CurrentTechnique = nullptr;
		return;
    }

    HR_ASSERT(D3DX11CreateEffectFromMemory(effectBlob->GetBufferPointer(), effectBlob->GetBufferSize(), 0, &m_Renderer.getD3DDevice(), &m_Effect));

    m_CurrentTechnique = m_Effect->GetTechniqueByIndex(0);
}

void DXShaderProgram::useTechnique(const string& name)
{
    m_CurrentTechnique = m_Effect->GetTechniqueByName(name.c_str());
    if(!m_CurrentTechnique)
        THROW_EXCEPTION("no such technique is found: " + name);
}

UniformVarPtr DXShaderProgram::getVariable(const string& name)
{
    return make_aligned_shared<DXUniformVar>(m_Effect->GetVariableByName(name.c_str()));
}

TextureVarPtr DXShaderProgram::getTexture(const string& name)
{
    CE_ASSERT(m_Effect->IsValid(), "Expected valid texture.");
    ID3DX11EffectVariable* var = m_Effect->GetVariableByName(name.c_str());
    if(!var->IsValid())
        THROW_EXCEPTION("unknown variable: " + name);
    return make_aligned_shared<DXTextureVar>(var->AsShaderResource());
}

void DXShaderProgram::getVariableType(const string& name, UniformVarTypeDesc& desc)
{
    D3DX11_EFFECT_TYPE_DESC fx_type_desc;
    ID3DX11EffectVariable* var = m_Effect->GetVariableByName(name.c_str());
    if(!var->IsValid())
        THROW_EXCEPTION("unknown variable: " + name);
    var->GetType()->GetDesc(&fx_type_desc);
    switch(fx_type_desc.Class)
    {
    case D3D_SVC_SCALAR:
        switch(fx_type_desc.Type)
        {
        case D3D_SVT_BOOL: desc.type = CE_UVT_BOOL; break;
        case D3D_SVT_INT: desc.type = CE_UVT_INT; break;
        case D3D_SVT_FLOAT: desc.type = CE_UVT_FLOAT; break;
        case D3D_SVT_UINT: desc.type = CE_UVT_UINT; break;
        default:
            THROW_EXCEPTION("variable: " + name + ": unsupported type");
        } break;
    case D3D_SVC_VECTOR:
    {
        assert(fx_type_desc.Columns && fx_type_desc.Rows == 1);
        switch(fx_type_desc.Type)
        {
        case D3D_SVT_BOOL:
            switch(fx_type_desc.Columns)
            {
            case 2: desc.type = CE_UVT_BVEC2; break;
            case 3: desc.type = CE_UVT_BVEC3; break;
            case 4: desc.type = CE_UVT_BVEC4; break;
            } break;
        case D3D_SVT_INT:
            switch(fx_type_desc.Columns)
            {
            case 2: desc.type = CE_UVT_IVEC2; break;
            case 3: desc.type = CE_UVT_IVEC3; break;
            case 4: desc.type = CE_UVT_IVEC4; break;
            } break;
        case D3D_SVT_FLOAT:
            switch(fx_type_desc.Columns)
            {
            case 2: desc.type = CE_UVT_VEC2; break;
            case 3: desc.type = CE_UVT_VEC3; break;
            case 4: desc.type = CE_UVT_VEC4; break;
            } break;
        case D3D_SVT_UINT:
            switch(fx_type_desc.Columns)
            {
            case 2: desc.type = CE_UVT_UVEC2; break;
            case 3: desc.type = CE_UVT_UVEC3; break;
            case 4: desc.type = CE_UVT_UVEC4; break;
            } break;
        default:
            THROW_EXCEPTION("variable: " + name + ": unsupported type");
        } break;
    }
    case D3D_SVC_MATRIX_COLUMNS:
    {
        assert(fx_type_desc.Type == D3D_SVT_FLOAT);
        if(fx_type_desc.Rows == 2 && fx_type_desc.Columns == 2) desc.type = CE_UVT_MAT2;
        else if(fx_type_desc.Rows == 3 && fx_type_desc.Columns == 3) desc.type = CE_UVT_MAT3;
        else if(fx_type_desc.Rows == 4 && fx_type_desc.Columns == 4) desc.type = CE_UVT_MAT4;
        else if(fx_type_desc.Rows == 2 && fx_type_desc.Columns == 3) desc.type = CE_UVT_MAT2x3;
        else if(fx_type_desc.Rows == 2 && fx_type_desc.Columns == 4) desc.type = CE_UVT_MAT2x4;
        else if(fx_type_desc.Rows == 3 && fx_type_desc.Columns == 2) desc.type = CE_UVT_MAT3x2;
        else if(fx_type_desc.Rows == 3 && fx_type_desc.Columns == 4) desc.type = CE_UVT_MAT3x4;
        else if(fx_type_desc.Rows == 4 && fx_type_desc.Columns == 2) desc.type = CE_UVT_MAT4x2;
        else if(fx_type_desc.Rows == 4 && fx_type_desc.Columns == 3) desc.type = CE_UVT_MAT4x3;
    } break;
    default:
        THROW_EXCEPTION("variable: " + name + ": unsupported variable class");
    }
    desc.size = fx_type_desc.Elements;
}

void DXShaderProgram::getInputSignature(BYTE*& ptr, SIZE_T& size) const
{
    D3DX11_PASS_DESC pass_desc;
    HR_ASSERT(m_CurrentTechnique->GetPassByIndex(0)->GetDesc(&pass_desc));
    ptr = pass_desc.pIAInputSignature;
    size = pass_desc.IAInputSignatureSize;
}

void DXShaderProgram::apply() const
{
    HR_ASSERT(m_CurrentTechnique->GetPassByIndex(0)->Apply(0, &m_Renderer.getD3DCurrentDeviceContext()));
}

void DXTextureVar::set(TexturePtr* arr, size_t N)
{
    DynamicArray<ID3D11ShaderResourceView*> res_views;
    res_views.resize(N);
    for(size_t i = 0; i < N; ++i)
        res_views[i] = static_pointer_cast<DXTexture>(arr[i])->getResourceView();
    HR_ASSERT(m_Var->AsShaderResource()->SetResourceArray(res_views.get(), 0, N));
}

DXTexture::DXTexture(DXRenderer& rend, const TextureDescription& desc, const void* data, int flags)
{
    bool gen_mips = (flags & CE_GENERATE_MIPS) && data;
	DXGI_FORMAT format = D3DTranslateVarType(desc.Format);
    D3D11_SHADER_RESOURCE_VIEW_DESC srv_desc;

    CE_ASSERT(desc.Width > 0 && desc.Height > 0 && desc.Depth > 0, "All dimensions of a new texture must be greater than zero.");
    if(desc.Depth > 1 && (flags & (CE_TEXTURE_ARRAY | CE_TEXTURE_CUBE)) == 0)
    {
        srv_desc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE3D;
        srv_desc.Texture3D.MostDetailedMip = 0;
        srv_desc.Texture3D.MipLevels = flags & CE_GENERATE_MIPS ? log2(desc.Width) : 1;
        srv_desc.Format = format;

        D3D11_TEXTURE3D_DESC tex_desc;
		tex_desc.Width = desc.Width;
        tex_desc.Height = desc.Height;
        tex_desc.Depth = desc.Depth;
        tex_desc.MipLevels = !gen_mips;
        tex_desc.Format = format;
        tex_desc.Usage = D3D11_USAGE_DEFAULT;
        tex_desc.BindFlags = D3D11_BIND_SHADER_RESOURCE;
        tex_desc.CPUAccessFlags = 0;
        tex_desc.MiscFlags = gen_mips ? D3D11_RESOURCE_MISC_GENERATE_MIPS : 0;
        CE_ASSERT(desc.Samples <= 1, "Multisampling is unsupported for 3D textures.");

		switch(desc.Format)
        {
        case VT_D16:
        {
            srv_desc.Format = DXGI_FORMAT_D16_UNORM;
            tex_desc.BindFlags |= D3D11_BIND_DEPTH_STENCIL;
        } break;
        case VT_D24S8:
        {
            srv_desc.Format = DXGI_FORMAT_R24_UNORM_X8_TYPELESS;
            tex_desc.BindFlags |= D3D11_BIND_DEPTH_STENCIL;
        } break;
        case VT_D32:
        {
            srv_desc.Format = DXGI_FORMAT_D32_FLOAT;
            tex_desc.BindFlags |= D3D11_BIND_DEPTH_STENCIL;
        } break;
        default:
            tex_desc.BindFlags |= D3D11_BIND_RENDER_TARGET; // fixme
            srv_desc.Format = format;
            break;
        }

		HR_ASSERT(rend.getD3DDevice().CreateTexture3D(&tex_desc, nullptr, (ID3D11Texture3D**)&m_Texture));
    }
    else if(desc.Height > 1 && ((flags & CE_TEXTURE_ARRAY) == 0 || desc.Depth > 1))
    {
        D3D11_TEXTURE2D_DESC tex_desc;
        tex_desc.Width = desc.Width;
        tex_desc.Height = desc.Height;
        tex_desc.MipLevels = !gen_mips;
        tex_desc.ArraySize = desc.Depth;
        tex_desc.Format = format;
        tex_desc.SampleDesc.Count = desc.Samples;
        tex_desc.SampleDesc.Quality = -(desc.Samples > 1) & D3D11_STANDARD_MULTISAMPLE_PATTERN;
        tex_desc.Usage = D3D11_USAGE_DEFAULT;
        tex_desc.BindFlags = D3D11_BIND_SHADER_RESOURCE;
        tex_desc.CPUAccessFlags = 0;
        tex_desc.MiscFlags = gen_mips ? D3D11_RESOURCE_MISC_GENERATE_MIPS : 0;

        if(desc.Samples > 1)
        {
            CE_ASSERT((flags & (CE_TEXTURE_CUBE|CE_TEXTURE_ARRAY)) == 0, "Unsupported for texture arrays and cube maps.");
            srv_desc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2DMS;
        }
        else if(flags & CE_TEXTURE_CUBE)
        {
			CE_ASSERT(desc.Depth == 6, "Cube maps should always contain six faces.");
            srv_desc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D;
            srv_desc.TextureCube.MostDetailedMip = 0;
            srv_desc.TextureCube.MipLevels = flags & CE_GENERATE_MIPS ? log2(desc.Width) : 1;
            tex_desc.MiscFlags |= D3D11_RESOURCE_MISC_TEXTURECUBE;
        }
        else if(flags & CE_TEXTURE_ARRAY)
        {
            srv_desc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2DARRAY;
            srv_desc.Texture2DArray.MostDetailedMip = 0;
            srv_desc.Texture2DArray.MipLevels = flags & CE_GENERATE_MIPS ? log2(desc.Width) : 1;
            srv_desc.Texture2DArray.ArraySize = desc.Depth;
            srv_desc.Texture2DArray.FirstArraySlice = 0;
        }
        else
        {
            srv_desc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D;
            srv_desc.Texture2D.MostDetailedMip = 0;
            srv_desc.Texture2D.MipLevels = flags & CE_GENERATE_MIPS ? log2(desc.Width) : 1;
        }
        srv_desc.Format = format;

		switch(desc.Format)
        {
        case VT_D16:
        {
            srv_desc.Format = DXGI_FORMAT_D16_UNORM;
            tex_desc.BindFlags |= D3D11_BIND_DEPTH_STENCIL;
        } break;
        case VT_D24S8:
        {
            srv_desc.Format = DXGI_FORMAT_R24_UNORM_X8_TYPELESS;
            tex_desc.BindFlags |= D3D11_BIND_DEPTH_STENCIL;
        } break;
        case VT_D32:
        {
            srv_desc.Format = DXGI_FORMAT_D32_FLOAT;
            tex_desc.BindFlags |= D3D11_BIND_DEPTH_STENCIL;
        } break;
        default:
            tex_desc.BindFlags |= D3D11_BIND_RENDER_TARGET; // fixme
            srv_desc.Format = format;
            break;
        }

		HR_ASSERT(rend.getD3DDevice().CreateTexture2D(&tex_desc, nullptr, (ID3D11Texture2D**)&m_Texture));
    }
    else
    {
        assert((flags & CE_TEXTURE_CUBE) == 0);

        if(flags & CE_TEXTURE_ARRAY)
        {
            srv_desc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE1DARRAY;
            srv_desc.Texture1DArray.MostDetailedMip = 0;
            srv_desc.Texture1DArray.MipLevels = flags & CE_GENERATE_MIPS ? log2(desc.Width) : 1;
            srv_desc.Texture1DArray.ArraySize = desc.Height;
            srv_desc.Texture1DArray.FirstArraySlice = 0;
        }
        else
        {
            srv_desc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE1D;
            srv_desc.Texture1D.MostDetailedMip = 0;
            srv_desc.Texture1D.MipLevels = flags & CE_GENERATE_MIPS ? log2(desc.Width) : 1;
            srv_desc.Format = format;
        }

        D3D11_TEXTURE1D_DESC tex_desc;
        tex_desc.Width = desc.Width;
        tex_desc.MipLevels = !gen_mips;
        tex_desc.ArraySize = desc.Height;
        tex_desc.Format = format;
        tex_desc.Usage = D3D11_USAGE_DEFAULT;
        tex_desc.BindFlags = D3D11_BIND_SHADER_RESOURCE;
        tex_desc.CPUAccessFlags = 0;
        tex_desc.MiscFlags = gen_mips ? D3D11_RESOURCE_MISC_GENERATE_MIPS : 0;
        CE_ASSERT(desc.Samples <= 1, "Multisampling is unsupported for 1D textures");

		switch(desc.Format)
        {
        case VT_D16:
        {
            srv_desc.Format = DXGI_FORMAT_D16_UNORM;
            tex_desc.BindFlags |= D3D11_BIND_DEPTH_STENCIL;
        } break;
        case VT_D24S8:
        {
            srv_desc.Format = DXGI_FORMAT_R24_UNORM_X8_TYPELESS;
            tex_desc.BindFlags |= D3D11_BIND_DEPTH_STENCIL;
        } break;
        case VT_D32:
        {
            srv_desc.Format = DXGI_FORMAT_D32_FLOAT;
            tex_desc.BindFlags |= D3D11_BIND_DEPTH_STENCIL;
        } break;
        default:
            tex_desc.BindFlags |= D3D11_BIND_RENDER_TARGET; // fixme
            srv_desc.Format = format;
            break;
        }

        HR_ASSERT(rend.getD3DDevice().CreateTexture1D(&tex_desc, 0, (ID3D11Texture1D**)&m_Texture));
    }

    if(data)
	{
		auto type_size = SizeofVarType(desc.Format);
		rend.getD3DCurrentDeviceContext().UpdateSubresource(m_Texture, 0, 0, data, type_size*desc.Width, type_size*desc.Height*desc.Width);
	}
    HR_ASSERT(rend.getD3DDevice().CreateShaderResourceView(m_Texture, &srv_desc, &m_ResourceView));
    if((flags & CE_GENERATE_MIPS) && data)
        rend.getD3DCurrentDeviceContext().GenerateMips(m_ResourceView);
}

ComRAII<ID3D11DepthStencilView> DXTexture::createDepthStencilView(DXRenderer& renderer, size_t level, size_t layer)
{
    D3D11_SHADER_RESOURCE_VIEW_DESC desc;
    m_ResourceView->GetDesc(&desc);
    D3D11_DEPTH_STENCIL_VIEW_DESC ds_view_desc;
    ds_view_desc.Flags = 0;
    if(desc.Format == DXGI_FORMAT_R24_UNORM_X8_TYPELESS)
        ds_view_desc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
    else
        ds_view_desc.Format = desc.Format;
    switch(desc.ViewDimension)
    {
    case D3D11_SRV_DIMENSION_TEXTURE1D:
    {
        ds_view_desc.Texture1D.MipSlice = level;
        ds_view_desc.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE1D;
    } break;
    case D3D11_SRV_DIMENSION_TEXTURE1DARRAY: 
    {
        ds_view_desc.Texture1DArray.ArraySize = 1;
        ds_view_desc.Texture1DArray.FirstArraySlice = layer;
        ds_view_desc.Texture1DArray.MipSlice = level;
        ds_view_desc.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE1DARRAY;
    } break;
    case D3D11_SRV_DIMENSION_TEXTURE2D:
    {
        ds_view_desc.Texture2D.MipSlice = level;
        ds_view_desc.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2D;
    } break;
    case D3D11_SRV_DIMENSION_TEXTURE2DARRAY:
    {
        ds_view_desc.Texture2DArray.ArraySize = 1;
        ds_view_desc.Texture2DArray.FirstArraySlice = layer;
        ds_view_desc.Texture2DArray.MipSlice = level;
        ds_view_desc.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2DARRAY;
    } break;
    case D3D11_SRV_DIMENSION_TEXTURE2DMS:
    {
        ds_view_desc.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2DMS;
    } break;
    case D3D11_SRV_DIMENSION_TEXTURE2DMSARRAY:
    {
        ds_view_desc.Texture2DMSArray.ArraySize = 1;
        ds_view_desc.Texture2DMSArray.FirstArraySlice = layer;
        ds_view_desc.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2DMSARRAY;
    } break;
    default: assert(false); break;
    }
    ComRAII<ID3D11DepthStencilView> dsv;
    HR_ASSERT(renderer.getD3DDevice().CreateDepthStencilView(m_Texture, &ds_view_desc, &dsv));
    return dsv;
}

ComRAII<ID3D11RenderTargetView> DXTexture::createRenderTargetView(DXRenderer& renderer, size_t level, size_t layer)
{
    D3D11_SHADER_RESOURCE_VIEW_DESC desc;
    m_ResourceView->GetDesc(&desc);
    D3D11_RENDER_TARGET_VIEW_DESC rt_view_desc;
    rt_view_desc.Format = desc.Format;
    switch(desc.ViewDimension)
    {
    case D3D11_SRV_DIMENSION_BUFFER:
    {
        rt_view_desc.Buffer.ElementOffset = desc.Buffer.ElementOffset;
        rt_view_desc.Buffer.ElementWidth = desc.Buffer.ElementWidth;
        rt_view_desc.Buffer.FirstElement = desc.Buffer.FirstElement;
        rt_view_desc.Buffer.NumElements = desc.Buffer.NumElements;
        rt_view_desc.ViewDimension = D3D11_RTV_DIMENSION_BUFFER;
    } break;
    case D3D11_SRV_DIMENSION_TEXTURE1D:
    {
        rt_view_desc.Texture1D.MipSlice = level;
        rt_view_desc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE1D;
    } break;
    case D3D11_SRV_DIMENSION_TEXTURE1DARRAY:
    {
        rt_view_desc.Texture1DArray.ArraySize = 1;
        rt_view_desc.Texture1DArray.FirstArraySlice = layer;
        rt_view_desc.Texture1DArray.MipSlice = level;
        rt_view_desc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE1DARRAY;
    } break;
    case D3D11_SRV_DIMENSION_TEXTURE2D:
    {
        rt_view_desc.Texture2D.MipSlice = 0;
        rt_view_desc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE2D;
    } break;
    case D3D11_SRV_DIMENSION_TEXTURE2DARRAY:
    {
        rt_view_desc.Texture2DArray.ArraySize = 1;
        rt_view_desc.Texture2DArray.FirstArraySlice = layer;
        rt_view_desc.Texture2DArray.MipSlice = level;
        rt_view_desc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE2DARRAY;
    } break;
    case D3D11_SRV_DIMENSION_TEXTURE2DMS:
    {
        rt_view_desc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE2DMS;
    } break;
    case D3D11_SRV_DIMENSION_TEXTURE2DMSARRAY:
    {
        rt_view_desc.Texture2DMSArray.ArraySize = 1;
        rt_view_desc.Texture2DMSArray.FirstArraySlice = layer;
        rt_view_desc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE2DMSARRAY;
    } break;
    case D3D11_SRV_DIMENSION_TEXTURE3D:
    {
        rt_view_desc.Texture3D.FirstWSlice = layer;
        rt_view_desc.Texture3D.MipSlice = level;
        rt_view_desc.Texture3D.WSize = 1;
        rt_view_desc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE3D;
    } break;
    default: assert(false); break;
    }
    ComRAII<ID3D11RenderTargetView> rtv;
    renderer.getD3DDevice().CreateRenderTargetView(m_Texture, &rt_view_desc, &rtv);
    return rtv;
}

void TranslateUsage(VBUsage usage, UINT& d3d_cpu_access, D3D11_USAGE& d3d_usage)
{
    switch(usage)
    {
    default: assert(!"Unknown usage");
    case CE_STATIC_DRAW: d3d_cpu_access = 0, d3d_usage = D3D11_USAGE_DEFAULT; break;
    case CE_STATIC_READ: d3d_cpu_access = D3D11_CPU_ACCESS_READ, d3d_usage = D3D11_USAGE_STAGING; break;
    case CE_STATIC_COPY: d3d_cpu_access = 0, d3d_usage = D3D11_USAGE_IMMUTABLE; break;
    case CE_STREAM_DRAW: d3d_cpu_access = 0, d3d_usage = D3D11_USAGE_DEFAULT; break;
    case CE_STREAM_READ: d3d_cpu_access = D3D11_CPU_ACCESS_READ, d3d_usage = D3D11_USAGE_STAGING; break;
    case CE_STREAM_COPY: d3d_cpu_access = 0, d3d_usage = D3D11_USAGE_IMMUTABLE; break;
    case CE_DYNAMIC_DRAW: d3d_cpu_access = D3D11_CPU_ACCESS_WRITE, d3d_usage = D3D11_USAGE_DYNAMIC; break;
    case CE_DYNAMIC_READ: d3d_cpu_access = D3D11_CPU_ACCESS_READ, d3d_usage = D3D11_USAGE_STAGING; break;
    case CE_DYNAMIC_COPY: d3d_cpu_access = 0, d3d_usage = D3D11_USAGE_DYNAMIC; break;
    }
}

static D3D11_BIND_FLAG TranslateVBType(VBType vb_type)
{
    switch(vb_type)
    {
    default: assert(!"Unknown video buffer bind type"); return (D3D11_BIND_FLAG)0;
    case CE_VERTEX_BUFFER: return D3D11_BIND_VERTEX_BUFFER;
    case CE_INDEX_BUFFER: return D3D11_BIND_INDEX_BUFFER;
    }
}

DXVideoBuffer::DXVideoBuffer(DXRenderer& renderer, VBType type, size_t size, const void* data, VBUsage usage)
{
	UINT d3d_cpu_flags;
    D3D11_USAGE d3d_usage;
    TranslateUsage(usage, d3d_cpu_flags, d3d_usage);

    UINT bindflags = TranslateVBType(type);

    D3D11_BUFFER_DESC buf_desc;

    buf_desc.ByteWidth = size;
    buf_desc.Usage = d3d_usage;
    buf_desc.BindFlags = bindflags;
    buf_desc.CPUAccessFlags = d3d_cpu_flags;
    buf_desc.MiscFlags = 0;

    if(data)
    {
        D3D11_SUBRESOURCE_DATA sdata = { data, 0, 0 };
        HR_ASSERT(renderer.getD3DDevice().CreateBuffer(&buf_desc, &sdata, &m_Buffer));
    }
    else
    {
        HR_ASSERT(renderer.getD3DDevice().CreateBuffer(&buf_desc, nullptr, &m_Buffer));
    }
}

void DXVideoBuffer::bindVideoBuffer(DXRenderer& renderer, size_t stride, size_t offset) const
{
    assert(stride);
    renderer.getD3DCurrentDeviceContext().IASetVertexBuffers(0, 1, &m_Buffer, &stride, &offset);
}

void DXVideoBuffer::bindIndexBuffer(DXRenderer& renderer, DXGI_FORMAT format, size_t offset) const
{
    renderer.getD3DCurrentDeviceContext().IASetIndexBuffer(m_Buffer, format, offset);
}

DXStateObject::DXStateObject(DXRenderer& renderer, const MiscStateDescription& ce_desc)
{
    D3D11_RASTERIZER_DESC desc;
    desc.FrontCounterClockwise = TRUE;
	desc.DepthBias = static_cast<int>(round(ce_desc.DepthBiasUnits * 16777216)); // WARNING: Hardcoded!
    desc.DepthBiasClamp = 0.0f;
	desc.SlopeScaledDepthBias = ce_desc.DepthBiasFactor;
	desc.DepthClipEnable = ce_desc.DepthClip;
	desc.ScissorEnable = ce_desc.ScissorTest;
	desc.MultisampleEnable = ce_desc.MultiSampling;
	desc.AntialiasedLineEnable = ce_desc.LinesAntiAliasing;

	switch(ce_desc.PolygonFillMode)
    {
    case CE_FILL_SOLID: desc.FillMode = D3D11_FILL_SOLID; break;
    case CE_FILL_WIREFRAME: desc.FillMode = D3D11_FILL_WIREFRAME; break;
    default:
        CE_ASSERT(false, "Unknown polygon fill mode"); break;
    }

	switch(ce_desc.CullPolygonFace)
    {
    case CE_CULL_NONE: desc.CullMode = D3D11_CULL_NONE; break;
    case CE_CULL_FRONT: desc.CullMode = D3D11_CULL_FRONT; break;
    case CE_CULL_BACK: desc.CullMode = D3D11_CULL_BACK; break;
    default:
         CE_ASSERT(false, "Unknown polygon cull mode"); break;
    }

    HR_ASSERT(renderer.getD3DDevice().CreateRasterizerState(&desc, &m_RSState));
}

void DXStateObject::setup(DXRenderer& renderer)
{
    renderer.getD3DCurrentDeviceContext().RSSetState(m_RSState);
}

DXBlendStateObject::DXBlendStateObject(DXRenderer& renderer, const BlendStateDescription& ce_desc)
{
    D3D11_BLEND_DESC desc;
    ZeroMemory(&desc, sizeof(desc));
    desc.AlphaToCoverageEnable = FALSE;
	desc.RenderTarget[0].BlendEnable = ce_desc.BlendEnabled;

	desc.RenderTarget[0].SrcBlend = getDXBlend(ce_desc.SrcBlend);
	desc.RenderTarget[0].DestBlend = getDXBlend(ce_desc.DstBlend);
	desc.RenderTarget[0].BlendOp = getDXBlendOp(ce_desc.BlendOp);

	desc.RenderTarget[0].SrcBlendAlpha = getDXBlend(ce_desc.SrcBlendAlpha);
	desc.RenderTarget[0].DestBlendAlpha = getDXBlend(ce_desc.DstBlendAlpha);
	desc.RenderTarget[0].BlendOpAlpha = getDXBlendOp(ce_desc.BlendAlphaOp);
    desc.RenderTarget[0].RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL;
    for(size_t i = 1; i < 8; ++i)
        memcpy(desc.RenderTarget + i, desc.RenderTarget, sizeof(desc.RenderTarget[0]));

    renderer.getD3DDevice().CreateBlendState(&desc, &m_BlendState);
}

void DXBlendStateObject::setup(DXRenderer& renderer)
{
    FLOAT blend_factor[4] = { 0.0f, 0.0f, 0.0f, 0.0f };
    renderer.getD3DCurrentDeviceContext().OMSetBlendState(m_BlendState, blend_factor, 0xffffffff);
}

D3D11_BLEND DXBlendStateObject::getDXBlend(CEBlend blend)
{
    switch(blend)
    {
    default: assert(!"Unknown blend function"); return (D3D11_BLEND)0;
    case CE_BLEND_ZERO: return D3D11_BLEND_ZERO;
    case CE_BLEND_ONE: return D3D11_BLEND_ONE;
    case CE_BLEND_SRC_COLOR: return D3D11_BLEND_SRC_COLOR;
    case CE_BLEND_INV_SRC_COLOR: return D3D11_BLEND_INV_SRC_COLOR;
    case CE_BLEND_SRC_ALPHA: return D3D11_BLEND_SRC_ALPHA;
    case CE_BLEND_INV_SRC_ALPHA: return D3D11_BLEND_INV_SRC_ALPHA;
    case CE_BLEND_DEST_ALPHA: return D3D11_BLEND_DEST_ALPHA;
    case CE_BLEND_INV_DEST_ALPHA: return D3D11_BLEND_INV_DEST_ALPHA;
    case CE_BLEND_DEST_COLOR: return D3D11_BLEND_DEST_COLOR;
    case CE_BLEND_INV_DEST_COLOR: return D3D11_BLEND_INV_DEST_COLOR;
    case CE_BLEND_SRC_ALPHA_SAT: return D3D11_BLEND_SRC_ALPHA_SAT;
    case CE_BLEND_SRC1_COLOR: return D3D11_BLEND_SRC1_COLOR;
    case CE_BLEND_INV_SRC1_COLOR: return D3D11_BLEND_INV_SRC1_COLOR;
    case CE_BLEND_SRC1_ALPHA: return D3D11_BLEND_SRC1_ALPHA;
    case CE_BLEND_INV_SRC1_ALPHA: return D3D11_BLEND_INV_SRC1_ALPHA;
    }
}

D3D11_BLEND_OP DXBlendStateObject::getDXBlendOp(CEBlendOperation blendop)
{
    switch(blendop)
    {
    default: assert(!"Unknown blend operation"); return (D3D11_BLEND_OP)0;
    case CE_BLEND_OP_ADD: return D3D11_BLEND_OP_ADD;
    case CE_BLEND_OP_SUBTRACT: return D3D11_BLEND_OP_SUBTRACT;
    case CE_BLEND_OP_REV_SUBTRACT: return D3D11_BLEND_OP_REV_SUBTRACT;
    case CE_BLEND_OP_MIN: return D3D11_BLEND_OP_MIN;
    case CE_BLEND_OP_MAX: return D3D11_BLEND_OP_MAX;
    }
}

DXFramebuffer::DXFramebuffer(DXRenderer& renderer)
    :   m_Renderer(renderer)
{
}

void DXFramebuffer::attachDepthStencilTexture(const FBAttachmentInfo& info)
{   
    m_DepthStencilTexture = static_pointer_cast<DXTexture>(info.texture);
    m_DepthStencil = m_DepthStencilTexture->createDepthStencilView(m_Renderer, info.level, info.layer);
}

void DXFramebuffer::attachTextures(const FBAttachmentArray& textures)
{
    m_Textures.resize(textures.size());
    m_TextureViews.resize(textures.size());
    for(size_t i = 0; i < textures.size(); ++i)
    {
        m_Textures[i] = static_pointer_cast<DXTexture>(textures[i].texture);
        m_TextureViews[i] = m_Textures[i]->createRenderTargetView(m_Renderer, textures[i].level, textures[i].layer);
    }
}

void DXFramebuffer::bind()
{
    m_Renderer.getD3DCurrentDeviceContext().OMSetRenderTargets(m_TextureViews.size(), &m_TextureViews[0], m_DepthStencil);
}

void DXFramebuffer::clearBuffer(size_t idx, const Vector4& color)
{
    if(idx >= m_Textures.size())
        THROW_EXCEPTION("DirectX: Unknown framebuffer");
    m_Renderer.getD3DCurrentDeviceContext().ClearRenderTargetView(m_TextureViews[idx], color.elem);
}

void DXFramebuffer::clearAll(const Vector4& color)
{
    ID3D11DeviceContext& devctx = m_Renderer.getD3DCurrentDeviceContext();
    for(size_t i = 0; i < m_Textures.size(); ++i)
        devctx.ClearRenderTargetView(m_TextureViews[i], color.elem);
    devctx.ClearDepthStencilView(m_DepthStencil, D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL, 1.0f, 0); // TODO: check
}

bool DXFramebuffer::validate()
{
    return !m_TextureViews.empty();
}

void DXFramebuffer::clearDepthStencilBuffer(float depth, int stencil)
{
    m_Renderer.getD3DCurrentDeviceContext().ClearDepthStencilView(m_DepthStencil, D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL, 1.0f, 0);
}

DXDepthStencilStateObject::DXDepthStencilStateObject(DXRenderer& renderer,
                                                     const DepthStencilStateDescription& ce_desc)
	:   m_StencilRef(ce_desc.StencilRef)
{
    D3D11_DEPTH_STENCIL_DESC desc;
	desc.DepthEnable = ce_desc.DepthTest;
    desc.DepthWriteMask = ce_desc.DepthMask ? D3D11_DEPTH_WRITE_MASK_ALL : D3D11_DEPTH_WRITE_MASK_ZERO;
	desc.DepthFunc = getCompFunc(ce_desc.DepthFunc);
	desc.StencilEnable = ce_desc.StencilTest;
	desc.StencilWriteMask = ce_desc.StencilMask;
    desc.StencilReadMask = D3D11_DEFAULT_STENCIL_READ_MASK;
	desc.FrontFace.StencilFailOp = getStencilOp(ce_desc.FrontStencilFail);
	desc.FrontFace.StencilDepthFailOp = getStencilOp(ce_desc.FrontStencilDepthFail);
	desc.FrontFace.StencilPassOp = getStencilOp(ce_desc.FrontStencilDepthPass);
	desc.FrontFace.StencilFunc = getCompFunc(ce_desc.FrontStencilFunc);
	desc.BackFace.StencilFailOp = getStencilOp(ce_desc.BackStencilFail);
	desc.BackFace.StencilDepthFailOp = getStencilOp(ce_desc.BackStencilDepthFail);
	desc.BackFace.StencilPassOp = getStencilOp(ce_desc.BackStencilDepthPass);
	desc.BackFace.StencilFunc = getCompFunc(ce_desc.BackStencilFunc);

    HR_ASSERT(renderer.getD3DDevice().CreateDepthStencilState(&desc, &m_DepthStencilState));
}

void DXDepthStencilStateObject::setup(DXRenderer& renderer)
{
    renderer.getD3DCurrentDeviceContext().OMSetDepthStencilState(m_DepthStencilState, m_StencilRef);
}

D3D11_STENCIL_OP DXDepthStencilStateObject::getStencilOp(CEStencilOperation sop)
{
    switch(sop)
    {
    case CE_STENCIL_OP_KEEP: return D3D11_STENCIL_OP_KEEP;
    case CE_STENCIL_OP_ZERO: return D3D11_STENCIL_OP_ZERO;
    case CE_STENCIL_OP_REPLACE: return D3D11_STENCIL_OP_REPLACE;
    case CE_STENCIL_OP_INCR_SAT: return D3D11_STENCIL_OP_INCR_SAT;
    case CE_STENCIL_OP_DECR_SAT: return D3D11_STENCIL_OP_DECR_SAT;
    case CE_STENCIL_OP_INVERT: return D3D11_STENCIL_OP_INVERT;
    case CE_STENCIL_OP_INCR: return D3D11_STENCIL_OP_INCR;
    case CE_STENCIL_OP_DECR: return D3D11_STENCIL_OP_DECR;
    }
    assert(false);
    return (D3D11_STENCIL_OP)0;
}

D3D11_COMPARISON_FUNC DXDepthStencilStateObject::getCompFunc(CECompFunc sfunc)
{
    switch(sfunc)
    {
    case CE_CMP_NEVER: return D3D11_COMPARISON_NEVER;
    case CE_CMP_LESS: return D3D11_COMPARISON_LESS;
    case CE_CMP_EQUAL: return D3D11_COMPARISON_EQUAL;
    case CE_CMP_LESS_EQUAL: return D3D11_COMPARISON_LESS_EQUAL;
    case CE_CMP_GREATER: return D3D11_COMPARISON_GREATER;
    case CE_CMP_NOT_EQUAL: return D3D11_COMPARISON_NOT_EQUAL;
    case CE_CMP_GREATER_EQUAL: return D3D11_COMPARISON_GREATER_EQUAL;
    case CE_CMP_ALWAYS: return D3D11_COMPARISON_ALWAYS;
    }
    assert(false);
    return (D3D11_COMPARISON_FUNC)0;
}

DXIncludeProxyHandler::DXIncludeProxyHandler() {}
DXIncludeProxyHandler::~DXIncludeProxyHandler() {}

HRESULT __stdcall DXIncludeProxyHandler::Open(D3D_INCLUDE_TYPE, // Ignored, everything is handled the same. TODO: Split project from engine namespace.
                                              LPCSTR filename,
                                              LPCVOID, // Ignored
                                              LPCVOID *data,
                                              UINT *size)
{
	size_t s;
    auto ret = m_IncludeLoader->loadInclude(filename, reinterpret_cast<const char**>(data), s);
	*size = s;
	return ret ? S_OK : E_FAIL; 
}
    
HRESULT __stdcall DXIncludeProxyHandler::Close(LPCVOID pData)
{
    m_IncludeLoader->freeInclude(static_cast<const char*>(pData));
    return S_OK;
}

void DXIncludeProxyHandler::setIncludeLoader(const IncludeLoaderPtr& incl_loader)
{
    m_IncludeLoader = incl_loader;
}

IncludeLoaderPtr DXIncludeProxyHandler::getIncludeLoader()
{
    return m_IncludeLoader;
}
}
